package com.nulldev.util.other;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.bind.DatatypeConverter;

import com.nulldev.util.encoding.base64.Base64;
import com.nulldev.util.io.FileUtil;

public class ByteUtils {

	 public static byte[][] divideArray(byte[] source, int chunksize) {
	        byte[][] ret = new byte[(int)Math.ceil(source.length / (double)chunksize)][chunksize];

	        int start = 0;

	        for(int i = 0; i < ret.length; i++) {
	            ret[i] = Arrays.copyOfRange(source,start, start + chunksize);
	            start += chunksize ;
	        }

	        return ret;
	 }
	 
	 @Deprecated
	 /**
	  * Why is this here?
	  * Marked for REMOVAL
	  */
	 private static String[] splitStrings(String input, int chunksize) {
		 final ArrayList<String> output = new ArrayList<String>();
		 final byte[][] data = divideArray(input.getBytes(CharsetUtil.getCharset()), chunksize);
		 for (final byte[] b : data) {
			 final StringBuilder build = new StringBuilder();
			 for (final byte c : b) {
				 build.append((char) c);
			 }
			 output.add(input);
		 }
		 return ArrayUtils.arrayListToArray(output);
	 }

	 public static byte[] concatenateByteArrays(byte[] a, byte[] b) {
		    byte[] result = new byte[a.length + b.length];
		    System.arraycopy(a, 0, result, 0, a.length);
		    System.arraycopy(b, 0, result, a.length, b.length);
		    return result;
	}
	 
	 public static byte[] concatenateByteArrays(byte[]... arrays) {
		 byte[] a = arrays[0];
		 if (arrays.length > 1) {
			 for (int i = 1; i < arrays.length; i++) {
				 final byte[] b = arrays[i];
				 a = concatenateByteArrays(a, b);
			 }
		 }
		 return a;
	 }

	 public static int size(final byte[] source) {
		 final String s = new String(source, CharsetUtil.getCharset("UTF-16LE"));
		 return s.getBytes(CharsetUtil.getCharset("UTF-16LE")).length;
	 }

	public static byte[] create() {
		return new byte[] {};
	}
	
	public static byte[] create(final int size) {
		return new byte[size];
	}

	public static long calc(final byte[] bytes) {
		return bytes.length;
	}

	public static String calc(final byte[] bytes, final Charset charset) {
		return FileUtil.convertToStringRepresentation(bytes.length * 4);
	}

	public static byte[] fromBase64(String data) {
		if (data.startsWith("data:image/png;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/png;base64,"), "");
		}
		if (data.startsWith("data:image/jpg;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/jpg;base64,"), "");
		}
		if (data.startsWith("data:image/jpeg;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/jpeg;base64,"), "");
		}
		if (data.startsWith("data:image/gif;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/gif;base64,"), "");
		}
		try {
			return DatatypeConverter.parseBase64Binary(data);
		}catch (Exception e) {
			return Base64.decodeBase64(data);
		}
	}
	
	public static String toBase64(final byte[] data) {
		return DatatypeConverter.printBase64Binary(data);
	}
	
	public static byte[] fromHEX(final String data) {
		return DatatypeConverter.parseHexBinary(data);
	}
	
	public static String toHEX(final byte[] data) {
		return DatatypeConverter.printHexBinary(data);
	}
	
	public static final short byteArrayToShort(byte[] bytes) {
	    return ByteBuffer.wrap(bytes).getShort();
	}

	public static final int byteArrayToInt(byte[] bytes) {
	    return ByteBuffer.wrap(bytes).getInt();
	}

	public static final float byteArrayToFloat(byte[] bytes) {
	    return ByteBuffer.wrap(bytes).getFloat();
	}

	public static double byteArrayToDouble(byte[] bytes) {
	    return ByteBuffer.wrap(bytes).getDouble();
	}

	public static final long byteArrayToLong(byte[] bytes) {
	    return ByteBuffer.wrap(bytes).getLong();
	}
	
	public static byte[] toByteArray(double[] doubleArray){
	    int times = Double.SIZE / Byte.SIZE;
	    byte[] bytes = new byte[doubleArray.length * times];
	    for(int i=0;i<doubleArray.length;i++){
	        ByteBuffer.wrap(bytes, i*times, times).putDouble(doubleArray[i]);
	    }
	    return bytes;
	}

	public static double[] toDoubleArray(byte[] byteArray){
	    int times = Double.SIZE / Byte.SIZE;
	    double[] doubles = new double[byteArray.length / times];
	    for(int i=0;i<doubles.length;i++){
	        doubles[i] = ByteBuffer.wrap(byteArray, i*times, times).getDouble();
	    }
	    return doubles;
	}

	public static byte[] toByteArray(int[] intArray){
	    int times = Integer.SIZE / Byte.SIZE;
	    byte[] bytes = new byte[intArray.length * times];
	    for(int i=0;i<intArray.length;i++){
	        ByteBuffer.wrap(bytes, i*times, times).putInt(intArray[i]);
	    }
	    return bytes;
	}

	public static int[] toIntArray(final byte[] byteArray){
	    final int times = Integer.SIZE / Byte.SIZE;
	    int[] ints = new int[byteArray.length / times];
	    for(int i = 0; i < ints.length; i++){
	        ints[i] = ByteBuffer.wrap(byteArray, i * times, times).getInt();
	    }
	    return ints;
	}
	
	public static byte[] toByteArray(final long[] longArray) {
        final ByteBuffer bb = ByteBuffer.allocate(longArray.length * Long.BYTES);
        bb.asLongBuffer().put(longArray);
        return bb.array();
}
	
	public static long[] toLongArray(final byte[] inArr) { 
		long[] outArr = new long[inArr.length];
		int padding = outArr.length * 8 - inArr.length;
		ByteBuffer buffer = ByteBuffer.allocate(inArr.length + padding)
		                              .put(new byte[padding]).put(inArr);
		buffer.flip();
		buffer.asLongBuffer().get(outArr);
		return outArr;
	}
	
	public static byte[] readXBytes(byte[] bytes, int from, int to) {
		byte[] retBytes = new byte[to - from];
		int j = 0;
		for (int i = from; i < to; ++i) {
			retBytes[j++] = bytes[i];
		}
		return retBytes;
	}

	public static byte[] toByte(final Byte[] input) {
		byte[] out = new byte[input.length];
		for (int i = 0; i < input.length; i++) {
			final Byte t = input[i];
			out[i] = t.byteValue();
		}
		return out;
	}
	
	/**
     * to valid a char.
     * <li>1st bit: tagName\attribute name char</li>
     * <li>2st bit: useless char, such as whitespace\tab\return\newline</li>
     */
    private final static byte[] byteType = {
            0, // 0
            0, 0, 0, 0, 0, 0, 0, 0, // 1~8
            2, // 9: '\t'
            2, // 10: '\n'
            0, 0, // 11~12
            2, // 13: '\r'
            0, 0, 0, 0, 0, 0, 0, // 14~ 20
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21~30
            0, // 31
            2, // 32: ' '
            0, 0, 0, 0, 0, 0, 0, 0, // 33~40
            0, 0, 0, 0, // 41~44
            1, // 45: '-'
            1, // 46: '.'
            0, // 47
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48~57: '0'~'9'
            1, // 58: ':'
            0, 0, 0, 0, 0, 0, // 59~64
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 65~90: 'A'~'Z'
            0, 0, 0, 0, // 91~94
            1, // 95: '_'
            0, // 96
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 97~122: 'a'~'z'
    };
        /*
        SAPCE 32
        TAB 9
        RETURN 13
        NEWLINE 10
         */

    /**
     * check whether the byte is a char of tag name or attribute name.
     *
     * @param b a byte to be valid
     * @return true if valid, otherwise false
     */
    public final static boolean isValidTokenChar(final byte b) {
        // to check validChars
        return b >= 0 && b <= 122 && (byteType[b] & 1) > 0;
//        return b > 0 && ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
//                || b == ':' || b == '-' || b == '_' || b == '.');
    }

    /**
     * check whether the byte is ' ' or '\t' or '\r' or '\n'
     *
     * @param b a byte to be valid
     * @return
     */
    public final static boolean isWhiteSpaceOrNewLine(final byte b) {
        return b >= 0 && b <= 122 && (byteType[b] & 2) > 0;
    }
    
    private static final SecureRandom sr = new SecureRandom();
    
    public static byte[] randomBytes(final int length) {
    	byte[] rnd = ByteUtils.create(length);
    	sr.nextBytes(rnd);
    	return rnd;
    }

	public static byte[] add(byte[] data, final byte b) {
		return concatenateByteArrays(data, new byte[] {b});
	}
	
	public static String toBinary(final byte[] bytes) {
		final BigInteger big = new BigInteger(bytes);
		return big.toString(2);
	}

	public static byte[] fromList(final List<Byte> data) {
		byte[] out = new byte[data.size()];
		for (int i = 0; i < out.length; i++) {
			out[i] = data.get(i);
		}
		return out;
	}
}
